package top.zk123.chain.service.imp;

import com.alibaba.fastjson.JSON;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import top.zk123.chain.bean.*;
import top.zk123.chain.bean.returnObj.CertOnChain;
import top.zk123.chain.bean.returnObj.Upload2ChainListObj;
import top.zk123.chain.exception.*;
import top.zk123.chain.mapper.*;
import top.zk123.chain.service.CertificateService;
import top.zk123.chain.util.ExcelReader;
import top.zk123.chain.util.GlobalUtils;
import top.zk123.chain.util.HashUtil;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

import static top.zk123.chain.util.GlobalUtils.*;

/**
 * 证书相关 核心方法实现
 *
 * @author Ke Zhang
 * @since 1.0
 */
@Service
public class CertificateServiceImp implements CertificateService {
    private final String SALT = "#4d1*dlmmddewd@34%";
    private final String chainURL = "http://localhost:8080";
    @Resource
    CertificateMapper certificateMapper;
    @Resource
    CertificateTmpMapper certificateTmpMapper;
    @Resource
    UploadToChainMapper uploadToChainMapper;
    @Resource
    IssueMapper issueMapper;
    @Resource
    MessageMapper messageMapper;
    @Resource
    InstitutionMapper institutionMapper;
    @Resource
    RevokeMapper revokeMapper;
    @Resource
    RestTemplate restTemplate;

    @Override
    public ReturnInfo uploadExcel(MultipartFile file) throws WrongFileTypeException, FileEmptyException,
            FileUploadException {
        UploadPath uploadPath = GlobalUtils.fileUpload(file, TOMCAT_PATH + EXCEL_PATH);
        return new ReturnInfo(200, "文件上传成功", uploadPath);
    }

    @Override
    public ReturnInfo uploadImages(MultipartFile[] files, String imgUploadFolder) throws FileUploadException,
            WrongFileTypeException, FileEmptyException {
        List<UploadPath> uploadPaths = GlobalUtils.filesUpload(files, TOMCAT_PATH + imgUploadFolder);
        List<String> imagePath = new ArrayList<>();
        for (UploadPath up : uploadPaths) {
            imagePath.add(WEB_PATH + imgUploadFolder + up.getNewFileName());
        }
        return new ReturnInfo(200, "文件上传成功", imagePath);
    }

    @Override
    public List<UploadPath> uploadImagesAndGetPath(MultipartFile[] files, String imgUploadFolder) throws
            FileUploadException, WrongFileTypeException, FileEmptyException {
        return GlobalUtils.filesUpload(files, TOMCAT_PATH + imgUploadFolder);
    }

    @Override
    public ReturnInfo uploadSingleCert(Certificate certificate) {
        // 获取 InstitutionId
        String institutionId = GlobalUtils.getIdByToken();
        // 数据存储至 DB
        certificate.setInstitutionId(institutionId);
        certificate.setCertState(0);
        certificateMapper.save(certificate);
        Issue issue = new Issue();
        issue.setCertificateId(certificate.getCertId());
        issue.setInstitutionId(getIdByToken());
        issue.setDate(getCurrentDate_YMD());
        issueMapper.save(issue);
        return new ReturnInfo(200, "证书上传成功", certificate);
    }


    @Override
    public ReturnInfo parseMultipleCert(MultipartFile excel, MultipartFile[] userPic, MultipartFile[] certPic)
            throws FileUploadException, WrongFileTypeException, FileEmptyException,
            ExcelNotFoundException, ExcelParseErrorException, ExcelDataLinkToPicFailedException {
        // 删除临时数据库之前的数据
        deleteMultipleCerts();
        // 1.上传文件
        ReturnInfo excelInfo = uploadExcel(excel);
        List<UploadPath> userPicPathList = uploadImagesAndGetPath(userPic, USER_PIC_PATH);
        List<UploadPath> certPicPathList = uploadImagesAndGetPath(certPic, CERT_PIC_PATH);

        // 2. 解析Excel表格
        String excelPath = ((UploadPath) excelInfo.getData()).getNewFileName();
        List<ExcelData> excelDataList = ExcelReader.readExcel(TOMCAT_PATH + EXCEL_PATH + excelPath);

        // 3. 匹配对应的图片
        for (int i = 1; i < excelDataList.size(); i++) {
            ExcelData data = excelDataList.get(i);
            String originUserPic = data.getUserPic(); // 一寸照 原文件名
            String originCertPic = data.getCertPic(); // 证书扫描件 原文件名
            String newUserPic = null;
            String newCertPic = null;
            // 匹配 一寸照
            for (UploadPath path : userPicPathList) {
                if (path.getOriginFileName().equals(originUserPic)) {
                    newUserPic = path.getNewFileName();
                    break;
                }
            }
            // 匹配 证件扫描件
            for (UploadPath path : certPicPathList) {
                if (path.getOriginFileName().equals(originCertPic)) {
                    newCertPic = path.getNewFileName();
                    break;
                }
            }
            // 文件匹配失败,则删除上传的文件
            if (newCertPic == null || newUserPic == null) {
                // 删除 Excel
                deleteFile(excelPath, "excel//");
                // 删除 userPic
                for (UploadPath path : userPicPathList) {
                    deleteFile(path.getNewFileName(), "userPic//");
                }
                // 删除 certPic
                for (UploadPath path : certPicPathList) {
                    deleteFile(path.getNewFileName(), "certPic//");
                }
            }
            if (newUserPic == null) {
                throw new ExcelDataLinkToPicFailedException("一寸照匹配失败，出错的文件为: " + originUserPic);
            } else if (newCertPic == null) {
                throw new ExcelDataLinkToPicFailedException("证书扫描件匹配失败，出错的文件为: " + originCertPic);
            }
            data.setUserPic(WEB_PATH + USER_PIC_PATH + newUserPic);
            data.setCertPic(WEB_PATH + CERT_PIC_PATH + newCertPic);
        }
        // 获取用户 Id
        String institutionId = GlobalUtils.getIdByToken();
        // 删除首条示例数据
        excelDataList.remove(0);
        // 存储至 CertificateTmp
        for (ExcelData excelData : excelDataList) {
            CertificateTmp certificateTmp = GlobalUtils.ExcelData2CertificateTmp(excelData);
            // 上传证书后,证书状态默认为 0 - 未上链
            certificateTmp.setCertState(0);
            certificateTmp.setInstitutionId(institutionId);
            certificateTmpMapper.save(certificateTmp);
        }
        return new ReturnInfo(200, "证书解析成功", null);
    }

    @Override
    public ReturnInfo saveMultipleCert() {
        String institutionId = GlobalUtils.getIdByToken();
        // 从CertificateTmp 中查找数据
        List<CertificateTmp> dataList = certificateTmpMapper.findByInstitutionId(institutionId);
        // 转存至 Certificate 数据库
        Certificate certificate;
        for (CertificateTmp data : dataList) {
            certificate = GlobalUtils.certificateTmp2Certificate(data);
            Issue issue = new Issue();
            issue.setCertificateId(certificate.getCertId());
            issue.setInstitutionId(institutionId);
            issue.setDate(getCurrentDate_YMD_hms());
            issueMapper.save(issue);
            certificateMapper.save(certificate);
            certificateTmpMapper.delete(data);
        }
        return new ReturnInfo(200, "数据存储成功", null);
    }


    @Override
    public ReturnInfo getCertListByPage(int pageSize, int pageNum) {
        // 设定查询的规则 (第几页, 每页条数, 查询条件(按照 uid 升序排列))   ASC-升序  DESC - 降序
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.ASC, "uid"));
        Page<Certificate> pageList = certificateMapper.findByInstitutionId(GlobalUtils.getIdByToken(), pageable);
        String totalNum = String.valueOf(pageList.getTotalElements());
        List<Certificate> dataList = pageList.getContent();
        return new ReturnInfo(200, totalNum, JSON.toJSON(dataList));
    }


    @Override
    public ReturnInfo getTmpCertListByPage(int pageSize, int pageNum) {
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.ASC, "uid"));
        Page<CertificateTmp> pageList = certificateTmpMapper.findByInstitutionId(GlobalUtils.getIdByToken(), pageable);
        List<CertificateTmp> dataList = pageList.getContent();
        String totalNum = String.valueOf(pageList.getTotalElements());
        return new ReturnInfo(200, totalNum, JSON.toJSON(dataList));
    }

    @Override
    public ReturnInfo modifyCertTmp(CertificateTmp certificateTmp) {
        certificateTmpMapper.save(certificateTmp);
        return new ReturnInfo(200, "证书修改成功", certificateTmp);
    }


    @Override
    public ReturnInfo getCertListByQuery(int pageSize, int pageNum, String certType, String name,
                                         String sex, String userId, String certId, String issueDate,
                                         String institution) {
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize,
                Sort.by(Sort.Direction.ASC, "uid"));
        Page<Certificate> pageList = certificateMapper.findByInstitutionId(GlobalUtils.getIdByToken(),
                certType, name, sex, userId, certId, issueDate, institution, pageable);
        List<Certificate> dataList = pageList.getContent();
        String totalNum = String.valueOf(pageList.getTotalElements());
        return new ReturnInfo(200, totalNum, JSON.toJSON(dataList));
    }

    @Override
    public void deleteMultipleCerts() {
        String institutionId = GlobalUtils.getIdByToken();
        List<CertificateTmp> list = certificateTmpMapper.findByInstitutionId(institutionId);
        for (CertificateTmp tmp : list) {
            certificateTmpMapper.delete(tmp);
        }
    }

    @Override
    public ReturnInfo deleteCert(long UId) throws CertificateNotFoundException {
        Certificate certificate = certificateMapper.findByUid(UId);
        if (certificate == null) {
            throw new CertificateNotFoundException();
        }
        certificateMapper.delete(certificate);
        return new ReturnInfo(200, "证书删除成功", null);
    }

    @Override
    public ReturnInfo updateCert(Certificate certificate) {
        Certificate res = certificateMapper.findByInstitutionIdAndCertId(GlobalUtils.getIdByToken(),
                certificate.getCertId());
        certificateMapper.save(copyCert(res, certificate));
        return new ReturnInfo(200, "证书修改成功", certificate.getCertId());
    }

    @Override
    public Certificate copyCert(Certificate dest, Certificate resource) {
        dest.setInstitution(resource.getInstitution());
        dest.setCertType(resource.getCertType());
        dest.setCertState(resource.getCertState());
        dest.setInstitution(resource.getInstitution());
        dest.setIssueDate(resource.getIssueDate());
        dest.setCertId(resource.getCertId());
        dest.setEducation(resource.getEducation());
        dest.setAcademy(resource.getAcademy());
        dest.setMajor(resource.getMajor());
        dest.setAdmissionDate(resource.getAdmissionDate());
        dest.setGraduationDate(resource.getGraduationDate());
        dest.setName(resource.getName());
        dest.setSex(resource.getSex());
        dest.setBirth(resource.getBirth());
        dest.setUserId(resource.getUserId());
        return dest;
    }

    @Override
    public ReturnInfo upload2chain(List<Long> certUIdList) {
        for (long certUId : certUIdList) {
            // 修改证书当前状态
            Certificate certificate = certificateMapper.findByCertUid(certUId);
            certificate.setCertState(1);
            certificateMapper.save(certificate);
            // 证书上链
            UploadToChain upload = new UploadToChain();
            upload.setCertificateId(certificate.getCertId());
            upload.setDate(getCurrentDate_YMD_hms());
            // 保存上链信息
            uploadToChainMapper.save(upload);
            // 插入一条记录至 message 库
            Message message = new Message();
            message.setDate(getCurrentDate_YMD());
            message.setReceiver(certificate.getUserId());
            message.setSender(certificate.getInstitution());
            message.setState("未授权");
            message.setCert_uid(certificate.getUid());
            message.setType(certificate.getCertType());
            messageMapper.save(message);
        }

        return new ReturnInfo(200, "上链请求已提交", null);
    }


    @Override
    public ReturnInfo revokeCert(long certUId, String pwd, String reason) throws
            WrongPasswordException, CertificateNotFoundException, CertRevokedException {
        Institution institution = institutionMapper.findByInstitutionId(getIdByToken());
        String encryptPWD = new SimpleHash("SHA-512", pwd, this.SALT).toString();
        // 核验密码
        if (!encryptPWD.equals(institution.getPassword())) {
            throw new WrongPasswordException();
        }
        Certificate cert = certificateMapper.findByUid(certUId);
        if (cert == null) {
            throw new CertificateNotFoundException();
        }
        // 证书有效性检查
        int certState = cert.getCertState();
        if (certState == 3) {
            throw new CertRevokedException();
        }
        // 更新证书状态
        cert.setCertState(3);
        certificateMapper.save(cert);
        // 写入撤销记录
        Revoke revoke = new Revoke();
        revoke.setReason(reason);
        revoke.setUserId(cert.getUserId());
        revoke.setCert_uid(certUId);
        revoke.setDate(GlobalUtils.getCurrentDate_YMD());
        revokeMapper.save(revoke);
        return new ReturnInfo(200, "证书撤销成功", null);
    }


    @Override
    public ReturnInfo upload2ChainList(int pageSize, int pageNum, String certType, String name, String sex,
                                       String userId, String certId, String issueDate, String certState) {
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize,
                Sort.by(Sort.Direction.DESC, "uid"));
        Page<Certificate> certificates;
        if (certState.equals("")) {
            certificates = certificateMapper.findAllByInstitutionId(getIdByToken(), certType, name,
                    sex, userId, certId, issueDate, pageable);
        } else {
            int certState_int = Integer.parseInt(certState);
            certificates = certificateMapper.findByInstitutionId(getIdByToken(), certType, name, sex,
                    userId, certId, issueDate, certState_int, pageable);
        }

        List<Certificate> certList = certificates.getContent();
        Upload2ChainListObj res = new Upload2ChainListObj();
        res.setTotalSize(certificates.getTotalElements());
        res.setTotalPage(certificates.getTotalPages());
        res.setCertList(certList);
        return new ReturnInfo(200, "查询成功", JSON.toJSON(res));
    }

    @Override
    public ReturnInfo getCertDetail(long UId) throws CertificateNotFoundException {
        Certificate cert = certificateMapper.findByUid(UId);
        if (cert == null) {
            throw new CertificateNotFoundException();
        }
        return new ReturnInfo(200, "证书信息查询成功", JSON.toJSON(cert));
    }

    @Override
    public ReturnInfo uploadAllCertsToChain() {
        List<Long> certUIdList = certificateMapper.findCertsNotOnTheChain();
        return upload2chain(certUIdList);
    }

    @Override
    public ReturnInfo uploadCert2Chain(long certUId) throws CertificateNotFoundException, CertsExistOnChainException {
        Certificate cert = certificateMapper.findByCertUid(certUId);
        if (cert == null) {
            throw new CertificateNotFoundException();
        }
        List<CertOnChain> certsOnChain = getCertsOnChain();
        // API 基址
        String APIUrl = chainURL + "/" + "setcertificate";
        String fileHash = getFileHash(cert);
        if (certsOnChain != null) {
            for (CertOnChain certOnChain : certsOnChain) {
                if (certOnChain.getFileHash().equals(fileHash)) {
                    throw new CertsExistOnChainException();
                }
            }
        }

        String content = cert2CertOnChain(cert);
        // 接口调用完整网址
        String url = APIUrl + content;
        ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
        String res = entity.getBody();
        return new ReturnInfo(200, "证书上链成功", res);
    }

    @Override
    public List<CertOnChain> getCertsOnChain() {
        String url = chainURL + "/" + "getcertificate";
        ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
        String res = entity.getBody();
        if (res == null) {
            return null;
        }
        res = res.replaceAll("\n", "");
        if (res.equals("")) {
            return null;
        }
        String[] certStr = res.split(",");
        List<CertOnChain> certOnChainList = new ArrayList<>();
        for (String s : certStr) {
            String[] certArray = s.split("\\|");
            CertOnChain cert = new CertOnChain();
            cert.setName(certArray[0]);
            cert.setInstitution(certArray[1]);
            cert.setSex(certArray[2]);
            cert.setUserId(certArray[3]);
            cert.setBirth(certArray[4]);
            cert.setMajor(certArray[5]);
            cert.setCertId(certArray[6]);
            cert.setAdmissionDate(certArray[7]);
            cert.setGraduationDate(certArray[8]);
            cert.setAcademy(certArray[9]);
            cert.setFileHash(certArray[10]);
            cert.setIssueDate(certArray[11]);
            cert.setCertType(certArray[12]);
            cert.setCertState(Integer.parseInt(certArray[13]));
            certOnChainList.add(cert);
        }
        return certOnChainList;
    }

    @Override
    public String cert2CertOnChain(Certificate cert) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("/");
        buffer.append(cert.getUid());
        buffer.append("/");
        buffer.append(cert.getName());
        buffer.append("/");
        buffer.append(cert.getInstitution());
        buffer.append("/");
        buffer.append(cert.getSex());
        buffer.append("/");
        buffer.append(cert.getUserId());
        buffer.append("/");
        buffer.append(cert.getBirth());
        buffer.append("/");
        buffer.append(cert.getMajor());
        buffer.append("/");
        buffer.append(cert.getCertId());
        buffer.append("/");
        buffer.append(cert.getAdmissionDate());
        buffer.append("/");
        buffer.append(cert.getGraduationDate());
        buffer.append("/");
        buffer.append(cert.getAcademy());
        buffer.append("/");
        buffer.append(cert.getIssueDate());
        buffer.append("/");
        buffer.append(cert.getCertType());
        buffer.append("/");
        buffer.append(2);
        String str = buffer.toString();
        String fileHash = HashUtil.md5(str);
        buffer.append("/");
        buffer.append(fileHash);
        return buffer.toString();
    }
}
